﻿#define MONO_CECIL

#if MONO_CECIL
#else
#define _NET_REFLECTOR
#endif

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
using CallGraphExtractor.CecilAdapter;
using Reflector;
using Reflector.CodeModel;

namespace CallGraphExtractor
{
    public class Extractor
    {
        #region Fields

        private BackgroundWorker mExtractionWorker;
        private BackgroundWorker mGetMembersWorker;
        private BackgroundWorker mGetTypesWorker;
        private BackgroundWorker mGetNamespacesWorker;
        private BackgroundWorker mGetModulesWorker;
        private BackgroundWorker mGetAssembliesWorker;

        private AutoResetEvent mOnGetAssembliesDoneBlocker = new AutoResetEvent(false);
        private AutoResetEvent mOnGetModulesDoneBlocker = new AutoResetEvent(false);
        private AutoResetEvent mOnGetTypesDoneBlocker = new AutoResetEvent(false);
        private AutoResetEvent mOnGetMembersDoneBlocker = new AutoResetEvent(false);
        private AutoResetEvent mOnGetNamespacesDoneBlocker = new AutoResetEvent(false);
        private AutoResetEvent mOnExtractionDoneBlocker = new AutoResetEvent(false);

        #endregion Fields

        #region Lifecycle

        public Extractor()
        {
            this.ResultPackSize = 1000;
            this.CallTypesToUse =
                CallTypes.Call | CallTypes.Calli | CallTypes.Callvirt | CallTypes.NewObj | CallTypes.LdFld
                | CallTypes.NewArr;
            this.AssemblyPaths = new List<Uri>();
        }

        #endregion Lifecycle

        #region Properties

        /// <summary>
        /// A részeredmények maximális csomagmérete (get, set)
        /// </summary>
        public int ResultPackSize { get; set; }

        /// <summary>
        /// Gets the paths of the assemblies to extract the callgraph from.
        /// </summary>
        public List<Uri> AssemblyPaths { get; private set; }

        /// <summary>
        /// A vizsgálandó hívási típusok (get, set)
        /// </summary>
        public CallTypes CallTypesToUse { get; set; }

        private IServiceProvider pServiceProvider;
        private IServiceProvider ServiceProvider
        {
            get
            {
                if (pServiceProvider == null)
                    pServiceProvider = new ApplicationManager(null);
                return pServiceProvider;
            }
        }

        #endregion Properties

        #region Methods

        private DateTime mExtractTime = new DateTime();
        /// <summary>
        /// Aszinkron megkezdi a hivási gráf kinyerését.
        /// </summary>
        /// <remarks>
        /// A futási állaporól a <see cref="ExtractionProgressChanged"/>, a folyamat befejeztéről
        /// a <see cref="ExtractionDone"/> események tájékoztatnak.
        /// Részeredmények a <see cref="ExtractionSubresultExtracted"/> esemény eseményadatjában
        /// találhatók.
        /// </remarks>
        public void ExtractAsync()
        {
            if (this.AssemblyPaths == null)
                throw new InvalidOperationException("AssemblyPaths nincs megadva!");
            if (this.AssemblyPaths.Count == 0)
                throw new InvalidOperationException("Egyetlen asszembli elérési útja sincs megadva!");

            if (mExtractionWorker != null)
            {
                mExtractionWorker.CancelAsync();
                mExtractionWorker.Dispose();
            }
            mExtractionWorker = new BackgroundWorker();
            mExtractionWorker.WorkerSupportsCancellation = true;
            mExtractionWorker.WorkerReportsProgress = true;
            mExtractionWorker.DoWork += new DoWorkEventHandler(mExtractionWorker_DoWork);
            mExtractionWorker.ProgressChanged += new ProgressChangedEventHandler(mExtractionWorker_ProgressChanged);
            mExtractionWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(mExtractionWorker_RunWorkerCompleted);
            mExtractTime = DateTime.Now;
            mExtractionWorker.RunWorkerAsync();
        }

        private DateTime mGetMembersTime = new DateTime();
        public void GetMembersAsync()
        {
            if (this.AssemblyPaths == null)
                throw new InvalidOperationException("AssemblyPaths nincs megadva!");
            if (this.AssemblyPaths.Count == 0)
                throw new InvalidOperationException("Egyetlen asszembli elérési útja sincs megadva!");

            if (mGetMembersWorker != null)
            {
                mGetMembersWorker.CancelAsync();
                mGetMembersWorker.Dispose();
            }
            mGetMembersWorker = new BackgroundWorker();
            mGetMembersWorker.WorkerSupportsCancellation = true;
            mGetMembersWorker.WorkerReportsProgress = true;
            mGetMembersWorker.DoWork += new DoWorkEventHandler(mGetMembersWorker_DoWork);
            mGetMembersWorker.ProgressChanged +=
                new ProgressChangedEventHandler(mGetMembersWorker_ProgressChanged);
            mGetMembersWorker.RunWorkerCompleted
                += new RunWorkerCompletedEventHandler(mGetMembersWorker_RunWorkerCompleted);
            mGetMembersTime = DateTime.Now;
            mGetMembersWorker.RunWorkerAsync();
        }

        private DateTime mGetTypesTime = new DateTime();
        public void GetTypesAsync()
        {
            if (this.AssemblyPaths == null)
                throw new InvalidOperationException("AssemblyPaths nincs megadva!");
            if (this.AssemblyPaths.Count == 0)
                throw new InvalidOperationException("Egyetlen asszembli elérési útja sincs megadva!");

            if (mGetTypesWorker != null)
            {
                mGetTypesWorker.CancelAsync();
                mGetTypesWorker.Dispose();
            }
            mGetTypesWorker = new BackgroundWorker();
            mGetTypesWorker.WorkerSupportsCancellation = true;
            mGetTypesWorker.WorkerReportsProgress = true;
            mGetTypesWorker.DoWork += new DoWorkEventHandler(mGetTypesWorker_DoWork);
            mGetTypesWorker.ProgressChanged +=
                new ProgressChangedEventHandler(mGetTypesWorker_ProgressChanged);
            mGetTypesWorker.RunWorkerCompleted
                += new RunWorkerCompletedEventHandler(mGetTypesWorker_RunWorkerCompleted);
            mGetTypesTime = DateTime.Now;
            mGetTypesWorker.RunWorkerAsync();
        }

        private DateTime mGetNamespacesTime = new DateTime();
        public void GetNamespacesAsync()
        {
            if (this.AssemblyPaths == null)
                throw new InvalidOperationException("AssemblyPaths nincs megadva!");
            if (this.AssemblyPaths.Count == 0)
                throw new InvalidOperationException("Egyetlen asszembli elérési útja sincs megadva!");

            if (mGetNamespacesWorker != null)
            {
                mGetNamespacesWorker.CancelAsync();
                mGetNamespacesWorker.Dispose();
            }
            mGetNamespacesWorker = new BackgroundWorker();
            mGetNamespacesWorker.WorkerSupportsCancellation = true;
            mGetNamespacesWorker.WorkerReportsProgress = true;
            mGetNamespacesWorker.DoWork += new DoWorkEventHandler(mGetNamespacesWorker_DoWork);
            mGetNamespacesWorker.ProgressChanged +=
                new ProgressChangedEventHandler(mGetNamespacesWorker_ProgressChanged);
            mGetNamespacesWorker.RunWorkerCompleted
                += new RunWorkerCompletedEventHandler(mGetNamespacesWorker_RunWorkerCompleted);
            mGetNamespacesTime = DateTime.Now;
            mGetNamespacesWorker.RunWorkerAsync();
        }

        private DateTime mGetModulesTime = new DateTime();
        public void GetModulesAsync()
        {
            if (this.AssemblyPaths == null)
                throw new InvalidOperationException("AssemblyPaths nincs megadva!");
            if (this.AssemblyPaths.Count == 0)
                throw new InvalidOperationException("Egyetlen asszembli elérési útja sincs megadva!");

            if (mGetModulesWorker != null)
            {
                mGetModulesWorker.CancelAsync();
                mGetModulesWorker.Dispose();
            }
            mGetModulesWorker = new BackgroundWorker();
            mGetModulesWorker.WorkerSupportsCancellation = true;
            mGetModulesWorker.WorkerReportsProgress = true;
            mGetModulesWorker.DoWork += new DoWorkEventHandler(mGetModulesWorker_DoWork);
            mGetModulesWorker.ProgressChanged +=
                new ProgressChangedEventHandler(mGetModulesWorker_ProgressChanged);
            mGetModulesWorker.RunWorkerCompleted
                += new RunWorkerCompletedEventHandler(mGetModulesWorker_RunWorkerCompleted);
            mGetModulesTime = DateTime.Now;
            mGetModulesWorker.RunWorkerAsync();
        }

        private DateTime mGetAssembliesTime = new DateTime();
        public void GetAssembliesAsync()
        {
            if (this.AssemblyPaths == null)
                throw new InvalidOperationException("AssemblyPaths nincs megadva!");
            if (this.AssemblyPaths.Count == 0)
                throw new InvalidOperationException("Egyetlen asszembli elérési útja sincs megadva!");

            if (mGetAssembliesWorker != null)
            {
                mGetAssembliesWorker.CancelAsync();
                mGetAssembliesWorker.Dispose();
            }
            mGetAssembliesWorker = new BackgroundWorker();
            mGetAssembliesWorker.WorkerSupportsCancellation = true;
            mGetAssembliesWorker.WorkerReportsProgress = true;
            mGetAssembliesWorker.DoWork += new DoWorkEventHandler(mGetAssembliesWorker_DoWork);
            mGetAssembliesWorker.ProgressChanged +=
                new ProgressChangedEventHandler(mGetAssembliesWorker_ProgressChanged);
            mGetAssembliesWorker.RunWorkerCompleted
                += new RunWorkerCompletedEventHandler(mGetAssembliesWorker_RunWorkerCompleted);
            mGetAssembliesTime = DateTime.Now;
            mGetAssembliesWorker.RunWorkerAsync();
        }

        private void Help(bool extractCallGraph = false, bool getMembers = false, bool getTypes = false,
            bool getNamespaces = false, bool getModules = false, bool getAssemblies = false)
        {
            HashSet<CallGraphEdgeCollection> callGrahSubresult = null;
            HashSet<ExtendedMemberReference> membersSubresult = null;
            HashSet<ExtendedType> typesSubresult = null;
            HashSet<string> namespacesSubresult = null;
            HashSet<IModule> modulesSubresult = null;
            HashSet<IAssembly> assembliesSubresult = null;

            if (extractCallGraph == true)
                callGrahSubresult = new HashSet<CallGraphEdgeCollection>();//this.ResultPackSize);
            if (getMembers == true)
                membersSubresult = new HashSet<ExtendedMemberReference>();//this.ResultPackSize);
            if (getTypes == true)
                typesSubresult = new HashSet<ExtendedType>();//this.ResultPackSize);
            if (getNamespaces == true)
                namespacesSubresult = new HashSet<string>();//this.ResultPackSize);
            if (getModules == true)
                modulesSubresult = new HashSet<IModule>();//this.ResultPackSize);
            if (getAssemblies == true)
                assembliesSubresult = new HashSet<IAssembly>();

            //try
            //{
            List<KeyValuePair<IAssemblyManager, IAssembly>> assemblies =
                new List<KeyValuePair<IAssemblyManager, IAssembly>>(this.AssemblyPaths.Count);

            //IServiceProvider serviceProvider = new ApplicationManager(null);
            foreach (var path in this.AssemblyPaths)
            {
#if _NET_REFLECTOR
                IAssemblyManager assemblyLoader =
                    (IAssemblyManager)this.ServiceProvider.GetService(typeof(IAssemblyManager));
#elif MONO_CECIL
                Reflector.CodeModel.IAssemblyManager assemblyLoader =
                    new AssemblyLoader();
#endif
                assemblies.Add(
                    new KeyValuePair<IAssemblyManager, IAssembly>(
                        assemblyLoader,
                        assemblyLoader.LoadFile(path.LocalPath)));
            }

            int assembliesCount, modulesCount, typesCount, membersCount, namespacesCount;
            int assembliesDone = 0, modulesDone = 0, typesDone = 0, membersDone = 0;//, namespacesDone = 0;
            assembliesCount = Count(assemblies, getAssemblies);
            modulesCount = Count(assemblies, countModules: getModules);
            typesCount = Count(assemblies, countTypes: getTypes);
            membersCount = Count(assemblies, countMembers: getMembers);
            namespacesCount = Count(assemblies, countNamespaces: getNamespaces);
            if (getAssemblies == true)
                OnGetAssembliesProgressChanged(-1, 1);
            if (getModules == true)
                OnGetModulesProgressChanged(-1, 1);
            if (getTypes == true)
                OnGetTypesProgressChanged(-1, 1);
            if (getMembers == true)
                OnGetMembersProgressChanged(-1, 1);

            List<IModule> referredModules = null;
            //if (getModules == true || getAssemblies == true || getMembers == true
            //     || getTypes == true || getNamespaces == true)
            referredModules = GetReferredModules(assemblies);

            foreach (var assembly in assemblies)
            {
                if (getAssemblies == true)
                    assembliesSubresult.Add(assembly.Value);

                foreach (IModule module in assembly.Value.Modules)
                {
                    foreach (ITypeDeclaration typeDeclaration in module.Types)
                    {
                        // Extract callgraph
                        ForEachCall(extractCallGraph, getMembers, getTypes, getNamespaces, getModules,
                            getAssemblies, callGrahSubresult, membersSubresult, typesSubresult,
                            namespacesSubresult, modulesSubresult, assembliesSubresult, referredModules,
                            module, typeDeclaration);

                        if (getMembers == true || getNamespaces == true)
                        {
                            ForEachMember(membersSubresult, module, typeDeclaration, ref membersDone,
                                membersCount, getMembers, getNamespaces, namespacesSubresult);
                            foreach (ITypeDeclaration nested in typeDeclaration.NestedTypes)
                            {
                                ForEachMember(membersSubresult, module, nested, ref membersDone,
                                    membersCount, getMembers, getNamespaces, namespacesSubresult);
                            }
                        }
                        if (getTypes == true)
                        {
                            AddToTypesSubresult(typesSubresult, module, typeDeclaration);
                            foreach (ITypeDeclaration nested in typeDeclaration.NestedTypes)
                            {
                                AddToTypesSubresult(typesSubresult, module, nested, typeDeclaration);
                                OnGetTypesProgressChanged(++typesDone, typesCount);
                            }
                            OnGetTypesProgressChanged(++typesDone, typesCount);
                        }
                        if (getNamespaces == true)
                            AddToNamespacesSubresult(namespacesSubresult, typeDeclaration);
                    } // each typeDeclaration

                    if (getModules == true)
                    {
                        AddToModulesSubresult(modulesSubresult, module);
                        OnGetModulesProgressChanged(++modulesDone, modulesCount);
                    }
                    if (getAssemblies == true)
                    {
                        foreach (IAssemblyReference assemblyReference in module.AssemblyReferences)
                            AddToAssembliesSubresult(assembliesSubresult, assemblyReference.Resolve());
                    }
                } // each Module

                if (callGrahSubresult != null && callGrahSubresult.Count > 0)
                    OnExtractionSubresultExtracted(new List<CallGraphEdgeCollection>(callGrahSubresult));
                if (membersSubresult != null && membersSubresult.Count > 0)
                    OnGetMembersSubresultExtracted(new List<ExtendedMemberReference>(membersSubresult));
                if (typesSubresult != null && typesSubresult.Count > 0)
                    OnGetTypesSubresultExtracted(new List<ExtendedType>(typesSubresult));
                if (namespacesSubresult != null && namespacesSubresult.Count > 0)
                    OnGetNamespacesSubresultExtracted(new List<string>(namespacesSubresult));
                if (modulesSubresult != null && modulesSubresult.Count > 0)
                    OnGetModulesSubresultExtracted(new List<IModule>(modulesSubresult));
                if (assembliesSubresult != null)// && assembliesSubresult.Count > 0)
                    OnGetAssembliesSubresultExtracted(new List<IAssembly>(assembliesSubresult));

                assembly.Key.Unload(assembly.Value);
                if (getAssemblies == true)
                    OnGetAssembliesProgressChanged(++assembliesDone, assembliesCount);
            } // each Assembly
            //}
            //catch (Exception ex)
            //{
            //    Console.WriteLine("Fatal error: " + ex.Message);
            //    Console.ReadLine();
            //    //TODO_REVIEW: throw
            //    throw;
            //}
            if (getAssemblies == true)
                OnGetAssembliesDone(new RunWorkerCompletedEventArgs(null, null, false));
        }

        private int Count(List<KeyValuePair<IAssemblyManager, IAssembly>> assemblies,
            bool countAssemblies = false, bool countModules = false, bool countTypes = false,
            bool countMembers = false, bool countNamespaces = false)
        {
            HashSet<string> namespaces = null;
            if (countNamespaces == true)
                namespaces = new HashSet<string>();
            int count = 0;
            foreach (var assembly in assemblies)
            {
                foreach (IModule module in assembly.Value.Modules)
                {
                    foreach (ITypeDeclaration typeDeclaration in module.Types)
                    {
                        if (countMembers == true)
                        {
                            foreach (var eventDeclaration in typeDeclaration.Events)
                                count++;
                            foreach (var fieldDeclaration in typeDeclaration.Fields)
                                count++;
                            foreach (var methodDeclaration in typeDeclaration.Methods)
                                count++;
                            foreach (var propertyDeclaration in typeDeclaration.Properties)
                                count++;
                        }
                        if (countTypes == true)
                            count++;
                        if (countNamespaces == true
                            && namespaces.Contains(typeDeclaration.Namespace) == false)
                            namespaces.Add(typeDeclaration.Namespace);
                        foreach (ITypeDeclaration nested in typeDeclaration.NestedTypes)
                        {
                            if (countMembers == true)
                            {
                                foreach (var eventDeclaration in typeDeclaration.Events)
                                    count++;
                                foreach (var fieldDeclaration in typeDeclaration.Fields)
                                    count++;
                                foreach (var methodDeclaration in typeDeclaration.Methods)
                                    count++;
                                foreach (var propertyDeclaration in typeDeclaration.Properties)
                                    count++;
                            }
                            if (countTypes == true)
                                count++;
                        }
                    }
                    if (countModules == true)
                        count++;
                }
                if (countAssemblies == true)
                    count++;
            }
            if (countNamespaces == true)
                count += namespaces.Count;
            return count;
        }

        private void ForEachCall(bool extractCallGraph, bool getMembers, bool getTypes, bool getNamespaces,
            bool getModules, bool getAssemblies, HashSet<CallGraphEdgeCollection> callGrahSubresult,
            HashSet<ExtendedMemberReference> membersSubresult, HashSet<ExtendedType> typesSubresult,
            HashSet<string> namespacesSubresult, HashSet<IModule> modulesSubresult,
            HashSet<IAssembly> assembliesSubresult, List<IModule> referredModules, IModule module,
            ITypeDeclaration typeDeclaration)
        {
            foreach (IMethodDeclaration methodDeclaration in typeDeclaration.Methods)
            {
                ExtendedMemberReference caller =
                    new ExtendedMemberReference(module, methodDeclaration);
                IMethodBody methodBody = methodDeclaration.Body as IMethodBody;
                if (methodBody != null)
                {
                    List<CallGraphEdge> called = null;
                    if (extractCallGraph == true)
                        called = new List<CallGraphEdge>();
                    foreach (IInstruction instruction in methodBody.Instructions)
                    {
                        IMemberReference mr = instruction.Value as IMemberReference;
                        //if (getModules == true || getAssemblies == true || getMembers == true
                        //     || getTypes == true || getNamespaces == true)
                        if (mr != null)
                        {
                            IModule containerModule = GetContainerModule(mr, module, referredModules);
                            if (getNamespaces == true)
                            {
                                ITypeDeclaration td = null;
                                IArrayType at = null;
                                if (mr.DeclaringType.TryGetTypeDeclaration(out td) == true)
                                    AddToNamespacesSubresult(namespacesSubresult, td);
                                else if (mr.DeclaringType.TryGetArrayType(out at) == true)
                                    ;//AddToNamespacesSubresult(namespacesSubresult, at);
                            }
                            if (getAssemblies == true && containerModule != null)
                                AddToAssembliesSubresult(assembliesSubresult, containerModule.Assembly);
                            if (getModules == true && containerModule != null)
                                AddToModulesSubresult(modulesSubresult, containerModule);
                            if (getTypes == true)
                            {
                                ITypeDeclaration td = null;
                                IArrayType at = null;
                                if (mr.DeclaringType.TryGetTypeDeclaration(out td))
                                    AddToTypesSubresult(typesSubresult, containerModule, td);
                                else if (mr.DeclaringType.TryGetArrayType(out at))
                                    AddToTypesSubresult(typesSubresult, containerModule, at);
                            }
                            if (getMembers == true)
                                AddToMembersSubresult(membersSubresult, containerModule, mr);
                            if (extractCallGraph == true)
                            {
                                CallTypes callType = CallTypesExtension.Parse(instruction.Code);
                                if ((this.CallTypesToUse & callType) == callType
                                    && callType != CallTypes.None)
                                {
                                    called.Add(
                                        new CallGraphEdge(
                                            new ExtendedMemberReference(containerModule, mr),
                                            callType));
                                    //Console.WriteLine("  Calls to: " +
                                    //        instruction.Value.ToString());
                                }
                            }
                        }
                    }	// each instruction

                    if (extractCallGraph == true)
                    {
                        AddToCallGraphSubresult(callGrahSubresult, caller, called);
                    }
                }

            } // each methodDeclaration
        }

        #region Add to subresult

        private void AddToCallGraphSubresult(HashSet<CallGraphEdgeCollection> callGrahSubresult,
            ExtendedMemberReference caller, List<CallGraphEdge> called)
        {
#if DEBUG
            foreach (var item in called)
            {
                ITypeDeclaration td;
                if (item.ExtendedMemberReference.MemberReference.DeclaringType.TryGetTypeDeclaration(out td))
                {
                    if (td.Name.Contains("Object"))
                        ;
                }
            }
#endif
            CallGraphEdgeCollection cgec = new CallGraphEdgeCollection(caller, called);
            if (callGrahSubresult.Contains(cgec) == false)
                callGrahSubresult.Add(cgec);
            if (callGrahSubresult.Count >= this.ResultPackSize)
            {
                OnExtractionSubresultExtracted(new List<CallGraphEdgeCollection>(callGrahSubresult));
                callGrahSubresult.Clear();
            }
        }

        private void AddToTypesSubresult(HashSet<ExtendedType> typesSubresult, IModule module,
            IType type, IType outerType = null)
        {
            ExtendedType et = new ExtendedType(module, type, outerType);
            if (typesSubresult.Contains(et) && et.OuterType != null)
                typesSubresult.Remove(et);
            if (typesSubresult.Contains(et) == false)
                typesSubresult.Add(et);
            if (typesSubresult.Count >= this.ResultPackSize)
            {
                OnGetTypesSubresultExtracted(new List<ExtendedType>(typesSubresult));
                typesSubresult.Clear();
            }
        }

        private void AddToNamespacesSubresult(HashSet<string> namespacesSubresult,
            ITypeDeclaration typeDeclaration)
        {
            if (namespacesSubresult.Contains(typeDeclaration.Namespace) == false)
                namespacesSubresult.Add(typeDeclaration.Namespace);
            if (namespacesSubresult.Count >= this.ResultPackSize)
            {
                OnGetNamespacesSubresultExtracted(new List<string>(namespacesSubresult));
                namespacesSubresult.Clear();
            }
        }

        private void AddToAssembliesSubresult(HashSet<IAssembly> assembliesSubresult,
            IAssembly assembly)
        {
            if (assembliesSubresult.Contains(assembly) == false)
                assembliesSubresult.Add(assembly);
            if (assembliesSubresult.Count >= this.ResultPackSize)
            {
                OnGetAssembliesSubresultExtracted(
                    new List<IAssembly>(assembliesSubresult));
                assembliesSubresult.Clear();
            }
        }

        private void AddToModulesSubresult(HashSet<IModule> modulesSubresult, IModule module)
        {
            if (modulesSubresult.Contains(module) == false)
                modulesSubresult.Add(module);
            if (modulesSubresult.Count >= this.ResultPackSize)
            {
                OnGetModulesSubresultExtracted(new List<IModule>(modulesSubresult));
                modulesSubresult.Clear();
            }
        }

        private void AddToMembersSubresult(HashSet<ExtendedMemberReference> subresult, IModule module,
            IMemberReference memberReference)
        {
            ExtendedMemberReference emr = new ExtendedMemberReference(module, memberReference);
            if (subresult.Contains(emr) == false)
                subresult.Add(emr);
            if (subresult.Count >= this.ResultPackSize)
            {
                OnGetMembersSubresultExtracted(new List<ExtendedMemberReference>(subresult));
                subresult.Clear();
            }
        }

        #endregion Add to subresult

        private List<IModule> GetReferredModules(List<KeyValuePair<IAssemblyManager, IAssembly>> assemblies)
        {
            HashSet<IModule> referredModules = new HashSet<IModule>();

            foreach (var assembly in GetReferredAssemblies(assemblies))
            {
                foreach (IModule module in assembly.Modules)
                {
                    if (referredModules.Contains(module) == false)
                        referredModules.Add(module);
                }
            }
            return new List<IModule>(referredModules);
        }

        private List<IAssembly> GetReferredAssemblies(List<KeyValuePair<IAssemblyManager, IAssembly>> assemblies)
        {
            HashSet<IAssembly> referredAssemblies = new HashSet<IAssembly>();
            HashSet<IAssembly> ownAssemblies = new HashSet<IAssembly>();
            foreach (var item in assemblies)
            {
                ownAssemblies.Add(item.Value);
            }
            foreach (var item in assemblies)
            {
                foreach (IModule module in item.Value.Modules)
                {
                    foreach (IAssemblyReference assemblyReference in module.AssemblyReferences)
                    {
                        IAssembly referredAssembly = assemblyReference.Resolve();
                        if (ownAssemblies.Contains(referredAssembly) == false
                            && referredAssemblies.Contains(referredAssembly) == false)
                            referredAssemblies.Add(referredAssembly);
                    }
                }
            }
            referredAssemblies.UnionWith(ownAssemblies);
            return new List<IAssembly>(referredAssemblies);
        }

        private IModule GetContainerModule(IMemberReference mr, IModule module, List<IModule> referredModules)
        {
            ITypeDeclaration declaringType = null;
            if (mr.DeclaringType.TryGetTypeDeclaration(out declaringType))
            {
                foreach (ITypeDeclaration typeDeclaration in module.Types)
                {
                    if (typeDeclaration.CompareTo(declaringType) == 0)
                        return module;
                    foreach (ITypeDeclaration nested in typeDeclaration.NestedTypes)
                    {
                        if (nested.CompareTo(declaringType) == 0)
                            return module;
                    }
                }
                foreach (IModule m in referredModules)
                {
                    foreach (ITypeDeclaration typeDeclaration in m.Types)
                    {
                        if (typeDeclaration.CompareTo(declaringType) == 0)
                            return m;
                        foreach (ITypeDeclaration nested in typeDeclaration.NestedTypes)
                        {
                            if (nested.CompareTo(declaringType) == 0)
                                return m;
                        }
                    }
                }
            }
            return null;
        }

        private void HelpExtract()
        {
            Help(extractCallGraph: true);
        }

        private void HelpGetMembers()
        {
            Help(getMembers: true);
        }

        private void ForEachMember(HashSet<ExtendedMemberReference> subresult, IModule module,
            ITypeDeclaration typeDeclaration, ref int membersDone, int membersCount, bool getMembers,
            bool getNamespaces, HashSet<string> namespaceSubresult)
        {
            //TODO review: a methodDecl paramétereinek típusain kívül kell-e nézni a névtereket
            if (getMembers == true)
            {
                foreach (var eventDeclaration in typeDeclaration.Events)
                {
                    AddToMembersSubresult(subresult, module, eventDeclaration as IMemberReference);
                    OnGetMembersProgressChanged(++membersDone, membersCount);
                }
                foreach (var fieldDeclaration in typeDeclaration.Fields)
                {
                    AddToMembersSubresult(subresult, module, fieldDeclaration as IMemberReference);
                    OnGetMembersProgressChanged(++membersDone, membersCount);
                }
            }
            foreach (IMethodDeclaration methodDeclaration in typeDeclaration.Methods)
            {
                if (getMembers == true)
                {
                    AddToMembersSubresult(subresult, module, methodDeclaration as IMemberReference);
                    OnGetMembersProgressChanged(++membersDone, membersCount);
                }
                if (getNamespaces == true)
                    foreach (IParameterDeclaration parameterDeclaration in methodDeclaration.Parameters)
                    {
                        ITypeDeclaration td = null;
                        if (parameterDeclaration.ParameterType.TryGetTypeDeclaration(out td))
                        {
                            string ns = td.Namespace;
                            if (namespaceSubresult.Contains(ns) == false)
                                namespaceSubresult.Add(ns);
                        }
                        else
                        {
                            //Debugger.Break();
                            Debug.WriteLine("Unresolved parameter declaration: "
                                + parameterDeclaration.ParameterType.ToString());
                        }
                    }
            }
            if (getMembers == true)
                foreach (var propertyDeclaration in typeDeclaration.Properties)
                {
                    AddToMembersSubresult(subresult, module, propertyDeclaration as IMemberReference);
                    OnGetMembersProgressChanged(++membersDone, membersCount);
                }
        }

        private void HelpGetTypes()
        {
            Help(getTypes: true);
        }

        private void HelpGetNamespaces()
        {
            Help(getNamespaces: true);
        }

        private void HelpGetModules()
        {
            Help(getModules: true);
        }

        private void HelpGetAssemblies()
        {
            Help(getAssemblies: true);
        }

        #endregion Methods

        #region Events

        #region Extraction

        public event EventHandler<RunWorkerCompletedEventArgs> ExtractionDone;
        protected virtual void OnExtractionDone(RunWorkerCompletedEventArgs e)
        {
            mOnExtractionDoneBlocker.WaitOne();
            if (this.ExtractionDone != null)
                this.ExtractionDone(this, e);
        }

        public event EventHandler<ProgressChangedEventArgs> ExtractionProgressChanged;
        protected virtual void OnExtractionProgressChanged(ProgressChangedEventArgs e)
        {
            if (this.ExtractionProgressChanged != null)
                this.ExtractionProgressChanged(this, e);
        }

        protected void OnExtractionProgressChanged(int done, int max)
        {
            OnExtractionProgressChanged(
                new ProgressChangedEventArgs(
                    (int)(((double)done / (double)max) * 100.0), null));
        }

        public event EventHandler<SubresultEventArgs<CallGraphEdgeCollection>> ExtractionSubresultExtracted;
        protected virtual void OnExtractionSubresultExtracted(List<CallGraphEdgeCollection> subresult)
        {
            OnExtractionSubresultExtracted(new SubresultEventArgs<CallGraphEdgeCollection>(subresult));
        }

        protected virtual void OnExtractionSubresultExtracted(SubresultEventArgs<CallGraphEdgeCollection> e)
        {
            if (this.ExtractionSubresultExtracted != null)
                this.ExtractionSubresultExtracted(this, e);
            mOnExtractionDoneBlocker.Set();
        }

        #endregion Extraction

        #region GetMembers

        public event EventHandler<RunWorkerCompletedEventArgs> GetMembersDone;
        protected virtual void OnGetMembersDone(RunWorkerCompletedEventArgs e)
        {
            mOnGetMembersDoneBlocker.WaitOne();
            if (this.GetMembersDone != null)
                this.GetMembersDone(this, e);
        }

        public event EventHandler<ProgressChangedEventArgs> GetMembersProgressChanged;
        protected virtual void OnGetMembersProgressChanged(ProgressChangedEventArgs e)
        {
            if (this.GetMembersProgressChanged != null)
                this.GetMembersProgressChanged(this, e);
        }

        protected void OnGetMembersProgressChanged(int done, int max)
        {
            OnGetMembersProgressChanged(
                new ProgressChangedEventArgs(
                    (int)(((double)done / (double)max) * 100.0), null));
        }

        public event EventHandler<SubresultEventArgs<ExtendedMemberReference>> GetMembersSubresultExtracted;
        protected virtual void OnGetMembersSubresultExtracted(List<ExtendedMemberReference> subresult)
        {
            OnGetMembersSubresultExtracted(new SubresultEventArgs<ExtendedMemberReference>(subresult));
        }

        protected virtual void OnGetMembersSubresultExtracted(SubresultEventArgs<ExtendedMemberReference> e)
        {
            if (this.GetMembersSubresultExtracted != null)
                this.GetMembersSubresultExtracted(this, e);
            mOnGetMembersDoneBlocker.Set();
        }

        #endregion GetMembers

        #region GetTypes

        public event EventHandler<RunWorkerCompletedEventArgs> GetTypesDone;
        protected virtual void OnGetTypesDone(RunWorkerCompletedEventArgs e)
        {
            mOnGetTypesDoneBlocker.WaitOne();
            if (this.GetTypesDone != null)
                this.GetTypesDone(this, e);
        }

        public event EventHandler<ProgressChangedEventArgs> GetTypesProgressChanged;
        protected virtual void OnGetTypesProgressChanged(ProgressChangedEventArgs e)
        {
            if (this.GetTypesProgressChanged != null)
                this.GetTypesProgressChanged(this, e);
        }

        protected void OnGetTypesProgressChanged(int done, int max)
        {
            OnGetTypesProgressChanged(
                new ProgressChangedEventArgs(
                    (int)(((double)done / (double)max) * 100.0), null));
        }

        public event EventHandler<SubresultEventArgs<ExtendedType>> GetTypesSubresultExtracted;
        protected virtual void OnGetTypesSubresultExtracted(List<ExtendedType> subresult)
        {
            OnGetTypesSubresultExtracted(new SubresultEventArgs<ExtendedType>(subresult));
        }

        protected virtual void OnGetTypesSubresultExtracted(SubresultEventArgs<ExtendedType> e)
        {
            if (this.GetTypesSubresultExtracted != null)
                this.GetTypesSubresultExtracted(this, e);
            mOnGetTypesDoneBlocker.Set();
        }

        #endregion GetTypes

        #region GetNamespaces

        public event EventHandler<RunWorkerCompletedEventArgs> GetNamespacesDone;
        protected virtual void OnGetNamespacesDone(RunWorkerCompletedEventArgs e)
        {
            mOnGetNamespacesDoneBlocker.WaitOne();
            if (this.GetNamespacesDone != null)
                this.GetNamespacesDone(this, e);
        }

        public event EventHandler<ProgressChangedEventArgs> GetNamespacesProgressChanged;
        protected virtual void OnGetNamespacesProgressChanged(ProgressChangedEventArgs e)
        {
            if (this.GetNamespacesProgressChanged != null)
                this.GetNamespacesProgressChanged(this, e);
        }

        protected void OnGetNamespacesProgressChanged(int done, int max)
        {
            OnGetNamespacesProgressChanged(
                new ProgressChangedEventArgs(
                    (int)(((double)done / (double)max) * 100.0), null));
        }

        public event EventHandler<SubresultEventArgs<string>> GetNamespacesSubresultExtracted;
        protected virtual void OnGetNamespacesSubresultExtracted(List<string> subresult)
        {
            OnGetNamespacesSubresultExtracted(new SubresultEventArgs<string>(subresult));
        }

        protected virtual void OnGetNamespacesSubresultExtracted(SubresultEventArgs<string> e)
        {
            if (this.GetNamespacesSubresultExtracted != null)
                this.GetNamespacesSubresultExtracted(this, e);
            mOnGetNamespacesDoneBlocker.Set();
        }

        #endregion GetNamespaces

        #region GetModules

        public event EventHandler<RunWorkerCompletedEventArgs> GetModulesDone;
        protected virtual void OnGetModulesDone(RunWorkerCompletedEventArgs e)
        {
            mOnGetModulesDoneBlocker.WaitOne();
            if (this.GetModulesDone != null)
                this.GetModulesDone(this, e);
        }

        public event EventHandler<ProgressChangedEventArgs> GetModulesProgressChanged;
        protected virtual void OnGetModulesProgressChanged(ProgressChangedEventArgs e)
        {
            if (this.GetModulesProgressChanged != null)
                this.GetModulesProgressChanged(this, e);
        }

        protected void OnGetModulesProgressChanged(int done, int max)
        {
            OnGetModulesProgressChanged(
                new ProgressChangedEventArgs(
                    (int)(((double)done / (double)max) * 100.0), null));
        }

        public event EventHandler<SubresultEventArgs<IModule>> GetModulesSubresultExtracted;
        protected virtual void OnGetModulesSubresultExtracted(List<IModule> subresult)
        {
            OnGetModulesSubresultExtracted(new SubresultEventArgs<IModule>(subresult));
        }

        protected virtual void OnGetModulesSubresultExtracted(SubresultEventArgs<IModule> e)
        {
            if (this.GetModulesSubresultExtracted != null)
                this.GetModulesSubresultExtracted(this, e);
            mOnGetModulesDoneBlocker.Set();
        }

        #endregion GetModules

        #region GetAssemblies

        public event EventHandler<RunWorkerCompletedEventArgs> GetAssembliesDone;
        protected virtual void OnGetAssembliesDone(RunWorkerCompletedEventArgs e)
        {
            mOnGetAssembliesDoneBlocker.WaitOne();
            if (this.GetAssembliesDone != null)
                this.GetAssembliesDone(this, e);
        }

        public event EventHandler<ProgressChangedEventArgs> GetAssembliesProgressChanged;
        protected virtual void OnGetAssembliesProgressChanged(ProgressChangedEventArgs e)
        {
            if (this.GetAssembliesProgressChanged != null)
                this.GetAssembliesProgressChanged(this, e);
        }

        protected void OnGetAssembliesProgressChanged(int done, int max)
        {
            OnGetAssembliesProgressChanged(
                new ProgressChangedEventArgs(
                    (int)(((double)done / (double)max) * 100.0), null));
        }

        public event EventHandler<SubresultEventArgs<IAssembly>> GetAssembliesSubresultExtracted;
        protected virtual void OnGetAssembliesSubresultExtracted(List<IAssembly> subresult)
        {
            OnGetAssembliesSubresultExtracted(new SubresultEventArgs<IAssembly>(subresult));
        }

        protected virtual void OnGetAssembliesSubresultExtracted(SubresultEventArgs<IAssembly> e)
        {
            if (this.GetAssembliesSubresultExtracted != null)
                this.GetAssembliesSubresultExtracted(this, e);
            mOnGetAssembliesDoneBlocker.Set();
        }

        #endregion GetAssemblies

        #endregion Events

        #region EventHandlers

        #region mExtractionWorker

        void mExtractionWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            HelpExtract();
            Debug.WriteLine("[Time] Extract done after " + DateTime.Now.Subtract(mExtractTime).ToString());
        }

        void mExtractionWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            OnExtractionProgressChanged(e);
        }

        void mExtractionWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnExtractionDone(e);
        }

        #endregion mExtractionWorker

        #region mGetMembersWorker

        void mGetMembersWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            HelpGetMembers();
            Debug.WriteLine("[Time] GetMembers done after " + DateTime.Now.Subtract(mGetMembersTime).ToString());
        }

        void mGetMembersWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            OnGetMembersProgressChanged(e);
        }

        void mGetMembersWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnGetMembersDone(e);
        }

        #endregion mGetMembersWorker

        #region mGetTypesWorker

        void mGetTypesWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            HelpGetTypes();
            Debug.WriteLine("[Time] GetTypes done after " + DateTime.Now.Subtract(mGetTypesTime).ToString());
        }

        void mGetTypesWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            OnGetTypesProgressChanged(e);
        }

        void mGetTypesWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnGetTypesDone(e);
        }

        #endregion mGetTypesWorker

        #region mGetNamespacesWorker

        void mGetNamespacesWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            HelpGetNamespaces();
            Debug.WriteLine("[Time] GetNamespaces done after " + DateTime.Now.Subtract(mGetNamespacesTime).ToString());
        }

        void mGetNamespacesWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            OnGetNamespacesProgressChanged(e);
        }

        void mGetNamespacesWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnGetNamespacesDone(e);
        }

        #endregion mGetNamespacesWorker

        #region mGetModulesWorker

        void mGetModulesWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            HelpGetModules();
            Debug.WriteLine("[Time] GetModules done after " + DateTime.Now.Subtract(mGetModulesTime).ToString());
        }

        void mGetModulesWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            OnGetModulesProgressChanged(e);
        }

        void mGetModulesWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            OnGetModulesDone(e);
        }

        #endregion mGetModulesWorker

        #region mGetAssembliesWorker

        void mGetAssembliesWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            HelpGetAssemblies();
            Debug.WriteLine("[Time] GetAssemblies done after " + DateTime.Now.Subtract(mGetAssembliesTime).ToString());
        }

        void mGetAssembliesWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            OnGetAssembliesProgressChanged(e);
        }

        void mGetAssembliesWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //OnGetAssembliesDone(e);
        }

        #endregion mGetAssembliesWorker

        #endregion EventHandlers
    }
}
